/*
 * Nivel.cpp
 *
 *  Created on: 27/05/2012
 *      Author: danielspacek
 */

#include "Nivel"
#include <math.h>

//Nivel::Nivel(uchar numeroNivel, Aritmetico *aritmetico, Nivel* siguienteNivel)
Nivel::Nivel(uchar numeroNivel, Aritmetico *aritmetico)
{
	// Guardo las propiedades
	this->numeroNivel = numeroNivel;
	this->aritmetico = aritmetico;

	// Guardo el siguiente nivel
	// this->siguienteNivel = siguienteNivel;

	// Calculo los máximos y mínimos
	this->calcularMinMax();

	// Instancio la matriz de las estadísticas
	this->instanciarVector();
}

Nivel::~Nivel()
{
	// Libero la memoria
	delete[] this->vectorEstadisticas;
}

// void Nivel::Comprimir(uchar* caracter)
int Nivel::Descomprimir(uchar* c)
{
	// Descomprimo UN caracter
	// uchar c;
	int retValue;
	integer segmento;

	// Ejecuto el Aritmetico según la interfaz acordada (segmento: desde 0 ~ hasta totalRepeticiones-1)
	segmento = this->aritmetico->intervalo(this->totalRepeticiones);

	// Sumo 1 para saber la cantidad acumulada que estoy buscando (segmento: desde 1 ~ hasta totalRepeticiones)
	segmento++;

	// Obtener el índice correspondiente de la frecuencia devuelta por el aritmetico
	uchar index = this->indexFrecuenciaAcumulada(segmento);

	// Proceso primero el EOF para evitar leer caracteres de mas y tocarle el timbre al SegFault
	// Verifico si es el escape, entonces voy al siguiente Nivel
	if(index==this->indexEscape)
	{
		if(this->numeroNivel == NIVELES-1) throw EOF;
		retValue = 1;
		//c = this->siguienteNivel->Descomprimir();
	}else{
		//c = (uchar)this->valor(index);
		*c = (uchar)this->valor(index);
		retValue = 0;
	}

	// Estadísticas para que recalcule el aritmetico
	integer acumuladoHasta = this->acumuladoHastaIndex(index);
	integer repeticionesActual = this->vectorEstadisticas[index];
	this->interfazAritmetico(acumuladoHasta,repeticionesActual);

	// Luego de pasarle las estadisticas al aritmetico, calculo las nuevas
	this->aumentarEstadisticaPorIndex(index);

	// Verifico si hay que resetear las estadisticas
	this->verificarMaximoTecho();
	//return c;
	return retValue;
}

uchar Nivel::indexFrecuenciaAcumulada(integer segmento)
{
	// Recorro el Array y devuelvo el index correspondiente a la frecuencia acumulada
	integer acum = 0;
	uchar i = 0;

	while(acum<segmento) {
		acum += this->vectorEstadisticas[i++];
	}
	return i-1;		// El menos 1 es porque hacés el i++ luego de acumular, entonces ya te pasaste
}

//void Nivel::Comprimir(uchar* caracter)
int Nivel::Comprimir(uchar* caracter)
{
	// Casteo a uchar
	uchar valor = *caracter;

	int retValue;

	// Verifico si el caracter esta dentro del rango o emito Escape
	if(this->maximoValor < valor)
	{
		this->emitirEscape();
		//this->siguienteNivel->Comprimir(caracter);
		retValue = 1;
	}else{
		this->emitirCaracter(valor);
		retValue = 0;
	}
	this->verificarMaximoTecho();

	return retValue;
}

void Nivel::verificarMaximoTecho()
{
	// Verifico si la suma de las estadisticas es igual al maximo del techo
	if(this->totalRepeticiones >= (USHORT_MAX + 1)/2) this->reiniciarEstadisticas();
}

void Nivel::emitirCaracter(uchar valor)
{
	// Llamo al Aritmético para que emita según las estadisticas del valor
	integer acumuladoHasta = this->acumuladoHasta(valor);
	integer repeticionesActual = this->repeticiones(valor);

	// Aritmetico->Comprimir(this->acumuladoHasta(i),this->getRepeticiones(i),this->totalRepeticiones)
	//this->compresorAritmetico->prox(acumuladoHasta,repeticionesActual,repeticionesTotal);
	this->interfazAritmetico(acumuladoHasta, repeticionesActual);

	// Acumulo en el valor
	this->aumentarEstadisticaPorValor(valor);
}

void Nivel::interfazAritmetico(integer acumulado, integer repeticionesActual)
{
	// Aritmetico->Comprimir(this->acumuladoHasta(i),this->getRepeticiones(i),this->totalRepeticiones)
	this->aritmetico->proximo(acumulado, repeticionesActual, this->totalRepeticiones);
}

void Nivel::emitirEscape()
{
	// Llamo a la función del Aritmético que emita el escape y aumento la estadística
	integer acumuladoHastaEscape = this->acumuladoHastaIndex(this->indexEscape);
	integer repeticionesEscape = this->vectorEstadisticas[this->indexEscape];

	// Aritmetico->Comprimir(this->acumuladoHasta(i),this->getRepeticiones(i),this->totalRepeticiones)
	//this->compresorAritmetico->prox(acumuladoHasta,repeticionesActual,repeticionesTotal);
	this->interfazAritmetico(acumuladoHastaEscape, repeticionesEscape);

	// Aumento las estadísticas del escape
	this->aumentarEstadisticaEscape();
}

void Nivel::calcularMinMax()
{
	if(this->numeroNivel == 0){
		// Si el Nivel es 0 ==> 0
		this->minimoValor = 0;
		this->maximoValor = 0;
	}else{
		// Si no ==> MIN = 2^(n-1) y MAX = 2^n - 1
		this->minimoValor = pow(2,(this->numeroNivel - 1));
		this->maximoValor = pow(2,(this->numeroNivel)) - 1;
	}
}

void Nivel::instanciarVector()
{
	// Obtengo la cantidad de elementos (Max-Min+Bordes+Escape)
	uchar cantidad = this->maximoValor - this->minimoValor + 1 + 1;

	// Instancio el vector
	this->vectorEstadisticas = new integer[cantidad];

	// Inicializo los valores
	this->reiniciarEstadisticas();

	// Guardo la posición del ESCAPE
	this->indexEscape = cantidad - 1;

}

void Nivel::reiniciarEstadisticas()
{
	// Inicializo los valores
	uchar i;
	for(i=0;i<this->cantidadElementos();i++)
	{
		this->vectorEstadisticas[i] = 1;
	}

	// Inicializo el total de las repeticiones = cantidad;
	this->totalRepeticiones = this->cantidadElementos();
}

integer Nivel::acumuladoHastaIndex(uchar index)
{
	// Recorro hasta el inmediato anterior al index
	uchar i;
	integer acum = 0;
	for(i=0;i<index;i++)
	{
		acum += this->vectorEstadisticas[i];
	}

	return acum;
}

integer Nivel::acumuladoHasta(uchar valor)
{
	// Obtengo el index de ese valor
	uchar index = this->index(valor);

	// Devuelvo el acumulado hasta según el index
	return this->acumuladoHastaIndex(index);
}

uchar Nivel::index(uchar valor)
{
	// Valido que esté dentro del rango
	if(valor > this->maximoValor || valor < this->minimoValor) return -1;

	// Index = Valor-Minimo (Escape es el último)
	return (valor - this->minimoValor);
}

uchar Nivel::valor(uchar index)
{
	// Valido que esté dentro del rango
	if(index > this->cantidadElementos()) return -1;

	// Nunca voy a preguntar si es el escape, asi que devuelvo directamente el valor
	// Valor = Index+Minimo (Escape es el último)
	return (index + this->minimoValor);
}

uchar Nivel::cantidadElementos()
{
	// Devuelvo los elementos del vector, incluído el Escape (+1 por borde, +1 por Escape)
	return (this->maximoValor - this->minimoValor + 1 + 1);
}

integer Nivel::repeticiones(uchar valor)
{
	uchar index = this->index(valor);
	return this->vectorEstadisticas[index];
}

void Nivel::aumentarEstadisticaEscape()
{
	// Obtengo el index y aumento
	this->aumentarEstadisticaPorIndex(this->indexEscape);
}

void Nivel::aumentarEstadisticaPorValor(uchar valor)
{
	// Obtengo el index del valor
	uchar index = this->index(valor);
	this->aumentarEstadisticaPorIndex(index);
}

void Nivel::aumentarEstadisticaPorIndex(uchar index)
{
	// Aumento la estadística y el total
	this->vectorEstadisticas[index]++;
	this->totalRepeticiones++;
}

